import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import numpy as np
import random
from common.configs.tools import seed_num
torch.manual_seed(seed_num)
random.seed(seed_num)


class CBiLSTM(nn.Module):

    def __init__(self, args):
        super(CBiLSTM, self).__init__()
        self.args = args
        self.hidden_dim = args.lstm_hidden_dim
        self.num_layers = args.lstm_num_layers
        V = args.embed_num
        D = args.embed_dim
        C = args.class_num
        Ci = 1
        Co = args.kernel_num
        Ks = args.kernel_sizes

        if args.word_Embedding:
            self.vocab_size, self.embed_dim = args.pretrained_weight.shape
            self.embedding = nn.Embedding.from_pretrained(
                args.pretrained_weight)
        else:
            self.embed_dim = args.embed_dim
            self.embedding = nn.Embedding(num_embeddings=V,
                                          embedding_dim=self.embed_dim,
                                          padding_idx=0,
                                          max_norm=5.0)

        # CNN
        KK = []
        for K in Ks:
            KK.append(K + 1 if K % 2 == 0 else K)

        # self.convs1 = nn.ModuleList(
        #     [nn.Conv2d(Ci, Co, (K, D), stride=1, padding=(K//2, 0)) for K in KK])
        self.convs1 = nn.ModuleList([
            nn.Conv2d(Ci, D, (K, D), stride=1, padding=(K//2, 0)) for K in KK])
        # in channel = 1,
        # out channel = 200 (embed dim)
        # kernel size = (K, 200)

        # LSTM
        self.bilstm = nn.LSTM(D, self.hidden_dim, num_layers=self.num_layers,
                              dropout=args.dropout, bidirectional=True)

        self.fc = nn.Linear(self.hidden_dim * 2, self.hidden_dim // 2)

        # linear
        self.hidden2label1 = nn.Linear(self.hidden_dim * 2, self.hidden_dim)
        self.hidden2label2 = nn.Linear(self.hidden_dim, C)

        # dropout
        self.dropout = nn.Dropout(args.dropout)

    def forward(self, x):
        embed = self.embedding(x).float()
        # CNN
        embed = self.dropout(embed)
        cnn_x = embed
        cnn_x = cnn_x.unsqueeze(1)
        # N = 16
        # Ci = 1
        # H = 64
        # W = 200

        cnn_x = [F.relu(conv(cnn_x)).squeeze(3)
                 for conv in self.convs1]  # [(N,Co,W), ...]*len(Ks)
        # [(N=16,Co=D,Ho=64,Wo=1), ...]*len(Ks)
        # got 3 feature maps [(N=16,Co=D,Wo=64), ...]*len(Ks) after squeeze

        cnn_x = torch.cat(cnn_x, 2)
        cnn_x = torch.transpose(cnn_x, 1, 2)

        # BiLSTM
        bilstm_out, _ = self.bilstm(cnn_x)

        # bilstm_out = torch.transpose(bilstm_out, 0, 1)
        bilstm_out = torch.transpose(bilstm_out, 1, 2)

        bilstm_out = F.max_pool1d(bilstm_out, bilstm_out.size(2)).squeeze(2)
        # bilstm_out = torch.transpose(bilstm_out, 0, 1)
        # bilstm_out = self.fc(bilstm_out)
        # bilstm_out = torch.transpose(bilstm_out, 0, 1)

        # linear
        # print(bilstm_out.shape)
        cnn_bilstm_out = self.hidden2label1(torch.tanh(bilstm_out))
        cnn_bilstm_out = self.hidden2label2(torch.tanh(cnn_bilstm_out))

        # dropout
        logit = self.dropout(cnn_bilstm_out)

        return logit
